package com.lyf.gateway.filter;


import com.alibaba.fastjson.JSONObject;
import com.lyf.common.api.CommonResult;
import com.lyf.common.api.ResultCode;
import com.lyf.common.utils.JacksonUtil;
import com.lyf.gateway.config.GlobalSignConfig;
import com.lyf.gateway.model.CheckSignData;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.*;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;

import static com.lyf.gateway.constant.Constant.*;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.CACHED_REQUEST_BODY_ATTR;

@Component
@Slf4j
public class SignFilter implements GlobalFilter, Ordered {

    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private GlobalSignConfig globalSignConfig;
    @Value("${module-conf.sign-verify-url}")
    private String signVerifyUrl;

    @Override
    public int getOrder() {
        /**
         * 优先级高一些。token校验失败的请求直接拦截
         */
        return -101;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        String path = exchange.getRequest().getURI().getPath();
        ArrayList<String> keyword = globalSignConfig.getKeyword();
        ArrayList<String> signConfigUrls = globalSignConfig.getUrls();
        if (keyword.stream().anyMatch(el -> path.contains(el)) || signConfigUrls.stream().anyMatch(el -> path.startsWith(el))) {
            log.info("请求需要进行签名验证,请求路径:{}", path);
            String method = request.getMethod().name();
            HttpHeaders headers = request.getHeaders();

            MultiValueMap<String, String> queryParams = request.getQueryParams();
            CheckSignData checkSignData = new CheckSignData();
            checkSignData.setHttpMethod(method);
            checkSignData.setAppKey(headers.getFirst(ICE_AUTH_APPKEY));
            if (HttpMethod.POST.name().equals(method)) {
                checkSignData.setContentType(headers.getContentType().toString());
                /**
                 * 此处 通过exchange.getRequest().getBody() 已经无法进行读取body里面的数据了
                 * 所以通过全局过滤器进行强制缓存了请求body数据，所以此处就可以通过exchange.getAttribute进行直接获取
                  */
                DataBuffer dataBuffer = exchange.getAttribute(CACHED_REQUEST_BODY_ATTR);
                checkSignData.setContentData(resolveBody(dataBuffer));
            }
            Map<String, Object> headerMap = new HashMap<>(20);
            headerMap.put(ICE_AUTH_APPKEY, headers.getFirst(ICE_AUTH_APPKEY));
            headerMap.put(ICE_AUTH_TIMESTAMP, headers.getFirst(ICE_AUTH_TIMESTAMP));
            headerMap.put(ICE_AUTH_SIGN, headers.getFirst(ICE_AUTH_SIGN));
            checkSignData.setHeaderData(JSONObject.toJSONString(headerMap));
            checkSignData.setTimestamp(Long.valueOf(headers.getFirst(ICE_AUTH_TIMESTAMP)));
            checkSignData.setSign(headers.getFirst(ICE_AUTH_SIGN));
            checkSignData.setUrl(path);

            Map<String, Object> paramMap = new HashMap<>();
            for (String key : queryParams.keySet()) {
                paramMap.put(key, StringUtils.join(queryParams.get(key), ","));
            }
            if (paramMap.keySet().size() > 0) {
                checkSignData.setParameterData(JSONObject.toJSONString(paramMap));
            }
            CommonResult commResponse = this.checkSign(checkSignData);
            if (commResponse != null && commResponse.getCode() != ResultCode.SUCCESS.getCode()) {
                final String errMsg = "sign verify fail! " + commResponse.getMsg();
                log.warn("{}，请求拦截的地址是：{} ", errMsg, path);
                return this.unAuthoriziedResponse(response, errMsg);
            }

        }
        return chain.filter(exchange);
    }


    private String resolveBody(DataBuffer body) {
        AtomicReference<String> bodyRef = new AtomicReference<>();
        // 缓存读取的request body信息
        CharBuffer charBuffer = StandardCharsets.UTF_8.decode(body.asByteBuffer());
        bodyRef.set(charBuffer.toString());
        //获取request body
        return bodyRef.get();

    }

    private Mono<Void> unAuthoriziedResponse(ServerHttpResponse response, String message) {
        HttpStatus httpStatus = HttpStatus.UNAUTHORIZED;
        CommonResult commResponse = new CommonResult(httpStatus.value(), message);

        byte[] data = JacksonUtil.toJsonStringNoException(commResponse).getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(data);
        response.setStatusCode(httpStatus);

        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        return response.writeWith(Mono.just(buffer));
    }

    private CommonResult checkSign(CheckSignData checkSignData) {
        try {
            CompletableFuture<CommonResult> completableFuture = CompletableFuture.supplyAsync(
                    () -> {
                        try {
                            URI uri = UriComponentsBuilder.fromUriString(signVerifyUrl).build().toUri();
                            String body = JSONObject.toJSONString(checkSignData);
                            RequestEntity<String> requestEntity = RequestEntity.post(uri)
                                    .accept(MediaType.APPLICATION_JSON)
                                    .header("Content-Type", "application/json")
                                    .body(body);
                            ResponseEntity<CommonResult> responseResponseEntity = restTemplate.exchange(requestEntity, CommonResult.class);
                            return responseResponseEntity.getBody();
                        } catch (Exception e) {
                            log.error("http get verify token response throw ex:{}", e.getMessage());
                            return null;
                        }
                    }
            );
            if (completableFuture.get() != null) {
                return completableFuture.get();
            } else {
                return null;
            }
        } catch (Exception e) {
            log.error("verify token throw ex:{}", e.getMessage());
        }
        return null;
    }

}
